﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FunctionalLayer {
    /// <summary>
    /// This class is used to communicate with the Pit
    /// </summary>
    public class Pit {
        #region Fields
        //Alive message to send to the car
        const string _alive = "pit";
        //Split character
        const string _split = ";";
        //Acknowledge command message
        const string _acknowledge = "ack";

        /// <summary>
        /// De buffer data
        /// </summary>
        private Buffer _buffer;
        /// <summary>
        /// The buffer data that needs to be sent
        /// </summary>
        private BufferData _toSend;

        /// <summary>
        /// The port that will be used for the communication
        /// </summary>
        private SerialPort _port;

        /// <summary>
        /// Determines if a acknowledge is needed
        /// </summary>
        private bool _acknowledgeNeeded;
        #endregion

        public event Action<string> ExceptionOccured;

        /// <summary>
        /// Constructor
        /// </summary>
        public Pit()
        {
            try
            {
                _buffer = new Buffer();
                // Initiate
                _port = new SerialPort(ConfigSingleton.Settings.XBeePort, 115200, Parity.None, 8, StopBits.One);
                _port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
                _port.Open();

            }
            catch (Exception ex)
            {
                ExceptionOccured(ex.Message);
            }
        }

        /// <summary>
        /// Send data to pit
        /// </summary>
        /// <param name="data">data to send</param>
        public void SendToPit(BufferData data)
        {
            _buffer.AddDataBuffer(data);
        }

        /// <summary>
        /// New data received
        /// </summary>
        private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                if (_port.IsOpen)
                {
                    string data = _port.ReadLine();
                    if (data.Length >= 3)
                    {
                        messageReceived(data);
                    }
                }
            }
            catch (Exception ex)
            {
                ExceptionOccured(ex.Message);
            }
        }

        /// <summary>
        /// Interpret received message
        /// </summary>
        /// <param name="received">the received data</param>
        private void messageReceived(string received)
        {
            // If an alive is received send the BufferData _toSend
            // If _toSend is null get a new BufferData from the buffer
            if (received == _alive)
            {
                if (_toSend == null && !_buffer.Empty)
                {
                    _toSend = _buffer.ReadBuffer();
                }

                if (!_buffer.Empty)
                {
                    sendData(_toSend);
                }
            }
            // If an acknowledge is received, it means the the BufferData that is send is received successful!
            // Get a new BufferData from the buffer
            if (received == _acknowledge)
            {
                if (_acknowledgeNeeded)
                {
                    _acknowledgeNeeded = false;
                    if (!_buffer.Empty)
                    {
                        _toSend = _buffer.ReadBuffer();
                        sendData(_toSend);
                    }
                    else
                    {
                        _toSend = null;
                    }
                }
            }
        }

        /// <summary>
        /// Create a package and send data to the port
        /// </summary>
        /// <param name="data">bufferdata</param>
        private void sendData(BufferData data)
        {
            try
            {
                //Create byte[] message to send
                //Data
                byte[] message = createMessage(data);

                if (message.Length == 0)
                {
                    return;
                }

                _port.Write(message, 0, message.Length);
                _acknowledgeNeeded = true;

            }
            catch (Exception ex)
            {
                ExceptionOccured(ex.Message);
            }
        }

        /// <summary>
        /// Create a message
        /// </summary>
        /// <param name="data">bufferdata</param>
        /// <returns>message as byte array</returns>
        private byte[] createMessage(BufferData data)
        {
            string message = "";

            try
            {
                message += data.timeDate.ToString(CultureInfo.InvariantCulture) + _split;
                message += data.amperage.ToString(CultureInfo.InvariantCulture) + _split;
                message += data.distance.ToString(CultureInfo.InvariantCulture) + _split;
                message += data.section.ToString(CultureInfo.InvariantCulture) + _split;
                message += data.speed.ToString(CultureInfo.InvariantCulture) + _split;
                message += data.voltage.ToString(CultureInfo.InvariantCulture) + _split;
                message += data.wattage.ToString(CultureInfo.InvariantCulture) + _split;

                byte[] dataByte = Encoding.ASCII.GetBytes(message);
                byte[] messageByte = new byte[dataByte.Length + 2];
                messageByte[dataByte.Length] = 255;
                dataByte.CopyTo(messageByte, 0);
                messageByte[dataByte.Length + 1] = CRC.getChecksum(dataByte, dataByte.Length);

                return messageByte;

            }
            catch (Exception ex)
            {
                ExceptionOccured(ex.Message);
                return new byte[0];
            }
        }

        /// <summary>
        /// Close the port
        /// </summary>
        public void ClosePort()
        {
            try
            {
                if (_port.IsOpen)
                {
                    _port.Close();
                }
            }
            catch (Exception ex)
            {
                ExceptionOccured(ex.Message);
            }
        }
    }
}